package cn.starrysky108.simpleerp.core.util;

import cn.starrysky108.simpleerp.core.constant.SecurityConstants;
import cn.starrysky108.simpleerp.core.base.BaseResponseEntity;
import cn.starrysky108.simpleerp.core.enums.ResultCodeEnum;
import cn.starrysky108.simpleerp.core.exception.TokenException;
import cn.starrysky108.simpleerp.core.properties.SimpleErpProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;


/**
 * @author Carl Lee
 */
@Slf4j
@Component
public class JwtUtil {

    private static SimpleErpProperties.JwtProperties properties;

    @Autowired
    JwtUtil(SimpleErpProperties properties) {
        JwtUtil.properties = properties.getJwt();
    }

    /**
     * 获得token中的自定义信息，无需secret解密也能获得
     */
    public static <T> T getClaimFiled(String token, String filed, Class<T> clazz) {
        try {
            Claims claims = getClaims(token);
            assert claims != null;
            return claims.get(filed, clazz);
        } catch (Exception e) {
            return null;
        }

    }

    /**
     * 生成签名
     */
    public static String sign(String username, Long userId, long expireTime) {
        String token;
        Date nowDate = new Date();
        Date expireDate = new Date(nowDate.getTime() + 1000 * 60 * expireTime);
        // 附带userId信息
        token = Jwts.builder()
                .signWith(Keys.hmacShaKeyFor(getSecret()), SignatureAlgorithm.HS512)
                .setHeaderParam("typ", SecurityConstants.TOKEN_TYPE)
                .setIssuer(SecurityConstants.TOKEN_ISSUER)
                .setIssuedAt(nowDate)
                .setSubject(username)
                .claim(SecurityConstants.USER_Id_CLAIM, userId)
                .setExpiration(expireDate)
                .compact();
        return token;
    }

    /**
     * 解析token
     *
     * @param token token字符串
     * @return claims
     */
    public static Claims getClaims(String token) {
        try {
            return Jwts.parserBuilder().setSigningKey(getSecret()).build().parseClaimsJws(token).getBody();
        } catch (Exception e) {
            log.error("解析token失败,{}", e.getMessage());
            return null;
        }
    }

    /**
     * 生成accessToken
     *
     * @param username 用户Id
     * @return accessToken
     */
    public static String getAccessToken(String username, Long userId) {
        return sign(username, userId, properties.getAccessTokenExpireTime());
    }

    /**
     * 生成refreshToken
     *
     * @param userId 用户id
     * @return refreshToken
     */
    public static String getRefreshToken(String username, Long userId) {
        return sign(username, userId, properties.getRefreshTokenTokenExpireTime());
    }


    /**
     * 返回密钥
     */
    public static byte[] getSecret() {
        return properties.getSecret().getBytes();
    }

    /**
     * 获取用户id
     *
     * @param token token
     * @return userId
     */
    public static Long getUserId(String token) {
        return getClaimFiled(token, SecurityConstants.USER_ROLES_CLAIM, Long.class);
    }

    /**
     * 从已认证请求中获取用户id
     *
     * @return userId
     */
    public static Long getUserId(HttpServletRequest request) {
        String token = getHeaderToken(request);
        if (StringUtils.hasText(token)) {
            return getUserId(token);
        }
        return null;
    }

    /**
     * 返回角色名
     *
     * @param token token字符串
     * @return roles
     */
    public static String getUserName(String token) {
        Claims claims = getClaims(token);
        return ObjectUtils.isEmpty(claims) ? null : claims.getSubject();
    }

    /**
     * 获取token在多久后过期，单位毫秒
     *
     * @return token过期->0，非法token->-1，默认返回-1
     */
    public static long getExpire(String token) {
        long expire = -1L;
        if (!StringUtils.hasText(token)) {
            return expire;
        }
        try {
            expire = Jwts.parserBuilder().setSigningKey(getSecret()).build().parseClaimsJws(token).getBody().getExpiration().getTime() - System.currentTimeMillis();
        } catch (ExpiredJwtException e) {
            expire = 0;
        }
        return expire;
    }

    /**
     * 规范化token
     * 如果有token前缀，则去掉
     * 将空格转义为+
     * base64 编码中使用了加号（+），而 + 在 在服务器中被转码两次，导致由+变成了空格，因此造成了base64字符串被更改，在服务器端解码后就会出错。
     *
     * @param token 前端传入的token
     * @return 去除token前缀后的token，如果有
     */
    public static String normalizeToken(String token) {
        if (!StringUtils.hasText(token)) {
            return null;
        }
        String newToken = token.startsWith(SecurityConstants.TOKEN_PREFIX) ? token.replace(SecurityConstants.TOKEN_PREFIX, "") : token;
        return newToken.replaceAll(" ", "+");
    }

    /**
     * 获取请求头中token,并进行规范化后返回
     *
     * @param request HttpServletRequest
     * @return 去除token前缀后的token，如果有
     */
    public static String getHeaderToken(HttpServletRequest request) {

        return normalizeToken(request.getHeader(SecurityConstants.AUTH_HEADER));
    }

    /**
     * 对密码进行加密
     *
     * @param password 需要加密的字符串
     * @return 加密后的字符串
     */
    public static String encryptPassword(String password) {
        return new BCryptPasswordEncoder().encode(password);
    }

    /**
     * 验证accessToken是否合法
     * 发生错误则向前端返回错误信息
     *
     * @param accessToken token字符串
     * @return token鞋带的subject
     */
    public static String verify(String accessToken, HttpServletResponse response) {
        long expire = getExpire(accessToken);

        if (expire < 0) {
            HttpUtil.customResponse(BaseResponseEntity.failure(ResultCodeEnum.TOKEN_ILLEGAL), response);
            return null;
        } else if (expire == 0) {
            HttpUtil.customResponse(BaseResponseEntity.failure(ResultCodeEnum.TOKEN_EXPIRED), response);
            return null;
        } else {
            return getUserName(accessToken);
        }
    }

    /**
     * 验证refreshToken是否合法
     * 发生错误则直接抛出
     *
     * @param refreshToken token字符串
     */
    public static void verifyRefreshToken(String refreshToken) {
        try {
            Jwts.parserBuilder().setSigningKey(getSecret()).build().parseClaimsJws(refreshToken).getBody();
        } catch (ExpiredJwtException e) {
            //token过期
            throw new TokenException(refreshToken, ResultCodeEnum.REFRESH_TOKEN_EXPIRED);
        } catch (Exception e) {
            //其他验证错误
            throw new TokenException(refreshToken, ResultCodeEnum.TOKEN_ILLEGAL);
        }
    }
}
